﻿--[[
	This file is part of FlexBar3.

	FlexBar3 is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	FlexBar3 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with FlexBar3.  If not, see <http://www.gnu.org/licenses/>.
]]
-- Local upvalues
local type = type;
local select = select;
local assert = assert;
local rawget = rawget;
local setmetatable = setmetatable;

local OO = {};
FlexBar3.OO = OO;
-- Object Handlers, loop through all mixins and call said functions
-- Note: Only Initialize is handled by the OO API, the rest has to be called manualy.

local function safecall(Function, ...)
	if(type(Function) == "function")then
		return Function(...);
	end
end


-- Search for a certain key in the tables passed, return on first match
-- NB: because of the way this function works, if there is a name conflict (multiple classes being inheritated that declare a similary named variable, the one defined earlyest is used)
local function search (k, ...)
	for TableNum = 1, select("#", ...) do
		local Table = select(TableNum, ...);
		for i=1, #Table do
			local Match = Table[i][k];
			if(Match) then
				return Match;
			end
		end
	end
end

local Prototypes = {};
local MetaTables = {};
local Constructors = {};

-- Metatables rules
MetaTables.Class =
{
	__index = function(self, Key)
		return Prototypes.Class[Key] or search(Key, self.Parents);
	end
};
MetaTables.Object =
{
	__index = function(self, Key)
		return Prototypes.Object[Key] or self.Class[Key];
	end
};
MetaTables.ToClass = MetaTables.Class;
-- Prototype rules
Prototypes.Class =
{
	-- Initiate a new object of the declared class, any superfluous arguments are passed to the constructor if there is one
	new = function(self, ...)
		-- Create a table for the object and declare its metatable
		assert(rawget(self, "Virtual") ~= true, "Core.OO: Cannot instantiate a virtual class or an interface", 2)
		local Object = {};
		Object.Class = self;
		setmetatable(Object, MetaTables.Object);
		-- Check for interfaces and validate them
		if(self.Interface == true) then
			-- Recursivly check for interfaces and validate them if there are any
			Prototypes.Class.checkinterface(self, Object);
		end
		-- Construct the shit now we're sure its validated
		Prototypes.Class.construct(self, Object, ...);
		return Object;
	end,
	-- Recursive constructor
	construct = function(Class, Object, ...)
		safecall(rawget(Class, "__construct"), Object, ...);
		local Parents = Class.Parents;
		if(type(Parents) == "table") then
			for Key = 1, #Parents do
				Prototypes.Class.construct(Parents[Key], Object, ...);
			end
		end
	end,
	checkinterface = function(Table, Object)
		-- This table is an interface, validate it
		if(rawget(Table, "Interface") == true) then
			for Name, Type in pairs(Table) do
				assert(type(Object[Name]) == Type or Name == "Virtual" or Name == "Interface",
				("Core.OO: Interface implemented improperly, %s was expected to be a %s, was %s"):format(tostring(Name), tostring(Type), type(Object[Name])), 2);
			end
		-- Check if a parent of this class is an interface
		elseif(Table.Interface == true) then
			-- There is an interface out there somewhere, got to find it
			local Parents = Table.Parents;
			if(type(Parents) == "table") then
			for Key = 1, #Parents do
				Prototypes.Class.checkinterface(Parents[Key], Object);
			end
		end
	end
end
};
Prototypes.Object = {};

-- Create a class
Constructors.Class = function(self, ...)
	self.Parents = { ... };
end

Constructors.Interface = function(self)
	self.Interface = true;
	self.Virtual = true;
end

-- Create a class from a table passed by the user (override the default creation process)
OO.ToClass = function(Table, ...)
	setmetatable(Table, MetaTables.Class);
	Constructors.Class(Table, ...);
	return Table;
end

-- When something is called on OO, try to create something using the correct rules, if no rules are found, this will return an empty table
setmetatable(OO,
{
	__index = function(self, Key)
		return function(...)
			local New = {};
			-- Set the metatable to the matching metatable in our table
			if(MetaTables[Key]) then setmetatable(New, MetaTables[Key]); end
			if(Constructors[Key]) then Constructors[Key](New, ...); end
			return New;
		end
	end
});
